//     __ _____ _____ _____
//  __|  |   __|     |   | |  JSON for Modern C++
// |  |  |__   |  |  | | | |  version 3.11.3
// |_____|_____|_____|_|___|  https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT

#pragma once

#include <cstddef>    // nullptr_t
#include <exception>  // exception
#if JSON_DIAGNOSTICS
#include <numeric>  // accumulate
#endif
#include <nlohmann/detail/input/position_t.hpp>
#include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/detail/meta/cpp_future.hpp>
#include <nlohmann/detail/meta/type_traits.hpp>
#include <nlohmann/detail/string_concat.hpp>
#include <nlohmann/detail/string_escape.hpp>
#include <nlohmann/detail/value_t.hpp>
#include <stdexcept>  // runtime_error
#include <string>     // to_string
#include <vector>     // vector

NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail {

////////////////
// exceptions //
////////////////

/// @brief general exception of the @ref basic_json class
/// @sa https://json.nlohmann.me/api/basic_json/exception/
class exception : public std::exception {
 public:
  /// returns the explanatory string
  const char *what() const noexcept override { return m.what(); }

  /// the id of the exception
  const int
      id;  // NOLINT(cppcoreguidelines-non-private-member-variables-in-classes)

 protected:
  JSON_HEDLEY_NON_NULL(3)
  exception(int id_, const char *what_arg)
      : id(id_), m(what_arg) {}  // NOLINT(bugprone-throw-keyword-missing)

  static std::string name(const std::string &ename, int id_) {
    return concat("[json.exception.", ename, '.', std::to_string(id_), "] ");
  }

  static std::string diagnostics(std::nullptr_t /*leaf_element*/) { return ""; }

  template <typename BasicJsonType>
  static std::string diagnostics(const BasicJsonType *leaf_element) {
#if JSON_DIAGNOSTICS
    std::vector<std::string> tokens;
    for (const auto *current = leaf_element;
         current != nullptr && current->m_parent != nullptr;
         current = current->m_parent) {
      switch (current->m_parent->type()) {
      case value_t::array: {
        for (std::size_t i = 0;
             i < current->m_parent->m_data.m_value.array->size(); ++i) {
          if (&current->m_parent->m_data.m_value.array->operator[](i) ==
              current) {
            tokens.emplace_back(std::to_string(i));
            break;
          }
        }
        break;
      }

      case value_t::object: {
        for (const auto &element : *current->m_parent->m_data.m_value.object) {
          if (&element.second == current) {
            tokens.emplace_back(element.first.c_str());
            break;
          }
        }
        break;
      }

      case value_t::null:             // LCOV_EXCL_LINE
      case value_t::string:           // LCOV_EXCL_LINE
      case value_t::boolean:          // LCOV_EXCL_LINE
      case value_t::number_integer:   // LCOV_EXCL_LINE
      case value_t::number_unsigned:  // LCOV_EXCL_LINE
      case value_t::number_float:     // LCOV_EXCL_LINE
      case value_t::binary:           // LCOV_EXCL_LINE
      case value_t::discarded:        // LCOV_EXCL_LINE
      default:                        // LCOV_EXCL_LINE
        break;                        // LCOV_EXCL_LINE
      }
    }

    if (tokens.empty()) {
      return "";
    }

    auto str = std::accumulate(tokens.rbegin(), tokens.rend(), std::string{},
                               [](const std::string &a, const std::string &b) {
                                 return concat(a, '/', detail::escape(b));
                               });
    return concat('(', str, ") ");
#else
    static_cast<void>(leaf_element);
    return "";
#endif
  }

 private:
  /// an exception object as storage for error messages
  std::runtime_error m;
};

/// @brief exception indicating a parse error
/// @sa https://json.nlohmann.me/api/basic_json/parse_error/
class parse_error : public exception {
 public:
  /*!
  @brief create a parse error exception
  @param[in] id_       the id of the exception
  @param[in] pos       the position where the error occurred (or with
                       chars_read_total=0 if the position cannot be
                       determined)
  @param[in] what_arg  the explanatory string
  @return parse_error object
  */
  template <
      typename BasicJsonContext,
      enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
  static parse_error create(int id_, const position_t &pos,
                            const std::string &what_arg,
                            BasicJsonContext context) {
    const std::string w = concat(exception::name("parse_error", id_),
                                 "parse error", position_string(pos), ": ",
                                 exception::diagnostics(context), what_arg);
    return {id_, pos.chars_read_total, w.c_str()};
  }

  template <
      typename BasicJsonContext,
      enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
  static parse_error create(int id_, std::size_t byte_,
                            const std::string &what_arg,
                            BasicJsonContext context) {
    const std::string w =
        concat(exception::name("parse_error", id_), "parse error",
               (byte_ != 0 ? (concat(" at byte ", std::to_string(byte_))) : ""),
               ": ", exception::diagnostics(context), what_arg);
    return {id_, byte_, w.c_str()};
  }

  /*!
  @brief byte index of the parse error

  The byte index of the last read character in the input file.

  @note For an input with n bytes, 1 is the index of the first character and
        n+1 is the index of the terminating null byte or the end of file.
        This also holds true when reading a byte vector (CBOR or MessagePack).
  */
  const std::size_t byte;

 private:
  parse_error(int id_, std::size_t byte_, const char *what_arg)
      : exception(id_, what_arg), byte(byte_) {}

  static std::string position_string(const position_t &pos) {
    return concat(" at line ", std::to_string(pos.lines_read + 1), ", column ",
                  std::to_string(pos.chars_read_current_line));
  }
};

/// @brief exception indicating errors with iterators
/// @sa https://json.nlohmann.me/api/basic_json/invalid_iterator/
class invalid_iterator : public exception {
 public:
  template <
      typename BasicJsonContext,
      enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
  static invalid_iterator create(int id_, const std::string &what_arg,
                                 BasicJsonContext context) {
    const std::string w = concat(exception::name("invalid_iterator", id_),
                                 exception::diagnostics(context), what_arg);
    return {id_, w.c_str()};
  }

 private:
  JSON_HEDLEY_NON_NULL(3)
  invalid_iterator(int id_, const char *what_arg) : exception(id_, what_arg) {}
};

/// @brief exception indicating executing a member function with a wrong type
/// @sa https://json.nlohmann.me/api/basic_json/type_error/
class type_error : public exception {
 public:
  template <
      typename BasicJsonContext,
      enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
  static type_error create(int id_, const std::string &what_arg,
                           BasicJsonContext context) {
    const std::string w = concat(exception::name("type_error", id_),
                                 exception::diagnostics(context), what_arg);
    return {id_, w.c_str()};
  }

 private:
  JSON_HEDLEY_NON_NULL(3)
  type_error(int id_, const char *what_arg) : exception(id_, what_arg) {}
};

/// @brief exception indicating access out of the defined range
/// @sa https://json.nlohmann.me/api/basic_json/out_of_range/
class out_of_range : public exception {
 public:
  template <
      typename BasicJsonContext,
      enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
  static out_of_range create(int id_, const std::string &what_arg,
                             BasicJsonContext context) {
    const std::string w = concat(exception::name("out_of_range", id_),
                                 exception::diagnostics(context), what_arg);
    return {id_, w.c_str()};
  }

 private:
  JSON_HEDLEY_NON_NULL(3)
  out_of_range(int id_, const char *what_arg) : exception(id_, what_arg) {}
};

/// @brief exception indicating other library errors
/// @sa https://json.nlohmann.me/api/basic_json/other_error/
class other_error : public exception {
 public:
  template <
      typename BasicJsonContext,
      enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
  static other_error create(int id_, const std::string &what_arg,
                            BasicJsonContext context) {
    const std::string w = concat(exception::name("other_error", id_),
                                 exception::diagnostics(context), what_arg);
    return {id_, w.c_str()};
  }

 private:
  JSON_HEDLEY_NON_NULL(3)
  other_error(int id_, const char *what_arg) : exception(id_, what_arg) {}
};

}  // namespace detail
NLOHMANN_JSON_NAMESPACE_END
